/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.datawander.Presenter;

import com.db4o.reflect.ReflectClass;
import com.googlecode.datawander.View.ConnectionValues;
import com.googlecode.datawander.codecompiler.CodeCompiler;
import com.googlecode.datawander.codecompiler.CompileCodeException;
import com.googlecode.datawander.codeexecutor.CodeRunException;
import com.googlecode.datawander.codeexecutor.PersistenceCodeLoader;
import com.googlecode.datawander.codeexecutor.SqlCommand;
import com.googlecode.datawander.codeexecutor.SqlCommandException;
import com.googlecode.datawander.codeexecutor.SqlCommandExecutor;
import com.googlecode.datawander.codegenerator.CodeGenerationException;
import com.googlecode.datawander.codegenerator.DatabasesConnectorsGenerator;
import com.googlecode.datawander.codegenerator.OracleConnectorGenerator;
import com.googlecode.datawander.codegenerator.RelationalDatabaseScriptGenerator;
import com.googlecode.datawander.codegenerator.SourceCodeGenerator2Relational;
import com.googlecode.datawander.connectors.Db4oUtil;
import com.googlecode.datawander.connectors.MyRelationalDataBaseException;
import com.googlecode.datawander.connectors.OracleConnector;
import com.googlecode.datawander.i18n.I18n;
import com.googlecode.datawander.shemareader.Db4oDatabaseReader;
import java.io.File;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;

/**
 *
 * @author Marcin Stachniuk
 */
public class Object2RelationalMigrationThread extends Thread {

    private static final Logger logger = Logger.getLogger(Object2RelationalMigrationThread.class);
    private static final String DATABASE_SCRIPT_DIR = "generatedScripts";
    private static final String DATABASE_CREATE_SCRIPT_FILENAME = "CreateDatabaseScript.sql";
    private static final String DATABASE_INTEGRITY_SCRIPT_FILENAME = "IntegrityDatabaseScript.sql";
    private static final String GENERATED_CODE_FOLDER = "generatedCode";
    private static final String COMPILED_CODE_FOLDER = "compiledCode";

    private DataWanderViewInt dataWanderView;
    private ConnectionValues connectionValues;
    private DataWanderPresenterInt dataWanderPresenter;
    //TODO: calculate how must progress have value
    private int PROGRESS = 10;

    @Override
    public void run() {
        try {
            migrationProcess();
        } catch (CodeRunException ex) {
            logger.debug(ex, ex);
            dataWanderView.enterLog(I18n.getString("cannotinvokemethod"));
            dataWanderView.showErrorMessage();
        } catch(SqlCommandException ex) {
            logger.debug(ex, ex);
            dataWanderView.enterLog(I18n.getString("sqlexecutecommadproblem"));
            dataWanderView.showErrorMessage();
        } catch (ClassNotFoundException ex) {
            logger.debug(ex, ex);
            dataWanderView.enterLog(I18n.getString("classnotfoudproblem"));
            dataWanderView.showErrorMessage();
        } catch (CodeGenerationException ex) {
            logger.debug(ex, ex);
            dataWanderView.enterLog(I18n.getString("relationaldatabaseexceptionproblem"));
            dataWanderView.showErrorMessage();
        } catch (MyRelationalDataBaseException ex) {
            logger.debug(ex, ex);
            dataWanderView.enterLog(I18n.getString("relationaldatabaseexceptionproblem"));
            dataWanderView.showErrorMessage();
        } catch (CompileCodeException ex) {
            logger.debug(ex, ex);
            dataWanderView.enterLog(I18n.getString("nojavaccompiler"));
            dataWanderView.showErrorMessage();
        }
        finally {
            // inform controler about end of migration process
            dataWanderPresenter.endMigrationProcess();
            Db4oUtil.closeObjectContainer();
            Db4oUtil.shutdown();
        }
    }

    public void setDataWanderView(DataWanderViewInt dataWanderView) {
        this.dataWanderView = dataWanderView;
    }

    public void setConnectionValues(ConnectionValues connectionValues) {
        this.connectionValues = connectionValues;
    }

    public void setDataWanderPresenter(DataWanderPresenterInt dataWanderControler) {
        this.dataWanderPresenter = dataWanderControler;
    }

    public void migrationProcess() throws CompileCodeException, 
            MyRelationalDataBaseException, ClassNotFoundException,
            CodeGenerationException, SqlCommandException, CodeRunException {
        // test is javac instal
        CodeCompiler.isCompilerInstal();
        dataWanderView.enterLog(I18n.getString("compilationTestSuccess"));
        dataWanderView.showMigrationProggres(0);

        // init relational database driver
        OracleConnector.setUsername(connectionValues.getRelationalUsername());
        OracleConnector.setPassword(connectionValues.getRelationalPassword());
        OracleConnector.setUrl(connectionValues.getRelationalURL());
        OracleConnector.initDatabaseDriver();
        dataWanderView.enterLog(I18n.getString("relationaldriverloadedsucesfull"));
        dataWanderView.showMigrationProggres(PROGRESS);

        Db4oUtil.setDatabaseFilename(connectionValues.getObjectFilename());
        Db4oDatabaseReader db4oReader = new Db4oDatabaseReader();
        db4oReader.readDatabaseSchema();
        Set<ReflectClass> storedClasses = db4oReader.getUserClasses();
        if(storedClasses == null || storedClasses.size() == 0) {
            String message = I18n.getString("notfoundstoredclass");
            logger.info(message);
            throw new ClassNotFoundException(message);
        }
        dataWanderView.enterLog(I18n.getString("readallclassessuces"));
        dataWanderView.showMigrationProggres(2 * PROGRESS);

        PersistenceCodeLoader perCodeLoader = new PersistenceCodeLoader();
        perCodeLoader.setClassDirectory(connectionValues.getObjectJarOrCompiledFolder());
        perCodeLoader.loadAllNeededClass(storedClasses);
        Set<Class> loadedClassSet = perCodeLoader.getClassMap();
        dataWanderView.enterLog(I18n.getString("loadedallclassessucess"));
        dataWanderView.showMigrationProggres(3 * PROGRESS);

        // genrate database scripts
        RelationalDatabaseScriptGenerator relScriptGen = new RelationalDatabaseScriptGenerator();
        String userDir = System.getProperty("user.dir");
        String dir = userDir +
                (userDir.endsWith(File.separator) ? "" : File.separator) +
                DATABASE_SCRIPT_DIR;
        String databaseGeneratorScriptFile = dir + File.separator + DATABASE_CREATE_SCRIPT_FILENAME;
        new File(dir).mkdir();
        relScriptGen.generateCreateRelationalDatabaseScript(databaseGeneratorScriptFile,
                loadedClassSet);
        String databaseIntegrityScriptFile = dir + File.separator + DATABASE_INTEGRITY_SCRIPT_FILENAME;
        relScriptGen.generateReferentialIntegrityRelationalDatabaseScript(
                databaseIntegrityScriptFile, loadedClassSet);
        dataWanderView.enterLog(I18n.getString("realationaldatabasescriptgeneratesucess"));
        dataWanderView.showMigrationProggres(4 * PROGRESS);

        // execute creation database script
        Connection conn = OracleConnector.getConnection();
        SqlCommandExecutor sqlExecutor = new SqlCommandExecutor();
        List<SqlCommand> sqlCommands = sqlExecutor.readSqlCommands(databaseGeneratorScriptFile);
        for(SqlCommand cmd : sqlCommands) {
            try {
                sqlExecutor.executeCommand(conn, cmd);
            } catch(SqlCommandException e) {
                if(!cmd.isDropCommand()) {
                    throw e;
                }
            }
        }
        dataWanderView.enterLog(I18n.getString("sqlcommandallsucess"));
        dataWanderView.showMigrationProggres(5 * PROGRESS);


        // generate package name for Db4oUtil and OracleConecctor
        String pckName = generatePackageNameForConnectors(relScriptGen.getLoadedClassSet());
        generatePackageFolder(pckName, getSourceCodeFolder());

        // generate source code
        SourceCodeGenerator2Relational sourceGenerator = new SourceCodeGenerator2Relational();
        if(pckName != null) {
            sourceGenerator.addImportClassName(pckName + ".Db4oUtil");
            sourceGenerator.addImportClassName(pckName + ".OracleConnector");
        }

        sourceGenerator.generateSourceCode(relScriptGen, getSourceCodeFolder());
        Set<String> reloadClassNames = sourceGenerator.getAllToReloadClass();

        // geenrate Db4o source code
        DatabasesConnectorsGenerator db4oGenerator = new DatabasesConnectorsGenerator();
        db4oGenerator.setPackageName(pckName);
        db4oGenerator.generateDb4oUtilSource(new ArrayList<String>(reloadClassNames),
                getSourceCodeFolder());
        String db4oUtilClassname = db4oGenerator.getClassName();

        // generate Oracle Conector code
        OracleConnectorGenerator oracleGenerator = new OracleConnectorGenerator();
        oracleGenerator.setPackageName(pckName);
        oracleGenerator.generateOracleConnector(getSourceCodeFolder());
        String oracleClassname = oracleGenerator.getClassName();
        dataWanderView.enterLog(I18n.getString("allsourcegenerated"));
        dataWanderView.showMigrationProggres(6 * PROGRESS);

        // unload old classes
        relScriptGen.unloadClasses();
        sourceGenerator.unloadOldClasses();
        loadedClassSet = null;
        System.gc();
        Thread.yield();
        Db4oUtil.closeObjectContainer();
        Db4oUtil.shutdown();

        // compile source filder
        CodeCompiler codeCompiler = new CodeCompiler();
        codeCompiler.compileSourceCode(getSourceCodeFolder(), getCompileCodeFolder());
        dataWanderView.enterLog(I18n.getString("allsourcecompiled"));
        dataWanderView.showMigrationProggres(7 * PROGRESS);

        //reload Db4oUtil and OracleConnector
        Set<String> connectorsSet = new HashSet<String>();
        connectorsSet.add(oracleClassname);
        connectorsSet.add(db4oUtilClassname);
        perCodeLoader = new PersistenceCodeLoader();
        perCodeLoader.setClassDirectory(getCompileCodeFolder());
        perCodeLoader.loadConnectorsClasses(connectorsSet);
        // reload classes
        Set<Class> reloadedClasses = perCodeLoader.reloadAllNeededClass(reloadClassNames);
        dataWanderView.enterLog(I18n.getString("loadallcompiledcode"));
        dataWanderView.showMigrationProggres(8 * PROGRESS);
        

        // set parametrs to connect with databases.
        perCodeLoader.setDb4oFilename(connectionValues.getObjectFilename());
        perCodeLoader.setRealtionalDatabaseParametrs(
                connectionValues.getRelationalURL(),
                connectionValues.getRelationalUsername(),
                connectionValues.getRelationalPassword());

        // invoke method to data migrate
        for(Class cl : reloadedClasses) {
            //logger.debug(cl.getName() + " " + cl.getClassLoader().toString());
            Object allObjects = perCodeLoader.invokeLoadAllObjects(cl);
            logger.info("invoke load all boj OK");
            perCodeLoader.invokeSaveAllRecords(cl, allObjects);
            logger.info("invoke save all boj OK");
        }
        dataWanderView.enterLog(I18n.getString("migrationalldata"));
        dataWanderView.showMigrationProggres(9 * PROGRESS);

        // run script referential integrity
        sqlCommands = sqlExecutor.readSqlCommands(databaseIntegrityScriptFile);
        for(SqlCommand cmd : sqlCommands) {
            sqlExecutor.executeCommand(conn, cmd);
        }
        dataWanderView.enterLog(I18n.getString("sqlcommandallsucess"));
        dataWanderView.showMigrationProggres(10 * PROGRESS);

        Db4oUtil.closeObjectContainer();
        Db4oUtil.shutdown();

        dataWanderView.showSucessMessage();
    }

    private String getSourceCodeFolder() {
        String userDir = System.getProperty("user.dir");
        return userDir + File.separator + GENERATED_CODE_FOLDER;
    }

    private String getCompileCodeFolder() {
        String userDir = System.getProperty("user.dir");
        logger.trace("getCompileCodeFolder, userdir: " + userDir);
        return userDir + File.separator + COMPILED_CODE_FOLDER;
    }

    /**
     * Return package name, where will be Db4oUtil and OracleConnector geenrated.
     * @param loadedClassSet
     * @return
     */
    private String generatePackageNameForConnectors(Set<Class> loadedClassSet) {
        for(Class cl : loadedClassSet) {
            if(cl.getName().contains(".")) {
                return "dao";
            }
        }
        return null;
    }

    private String generatePackageFolder(String pckName, String sourceCodeFolder) {
        String dirname = null;
        if(pckName != null) {
            dirname = sourceCodeFolder +
                    (sourceCodeFolder.endsWith(File.separator) ? "" : File.separator) +
                    pckName.replace(".", File.separator);
            File f = new File(dirname);
            if(!f.exists()) {
                f.mkdirs();
            }
        }
        return dirname;
    }
}
